对于JVM来说,是通过JavaCalls模块来实现方法调用的。JavaCalls中有很多用来调用Java方法的函数,如call_virtual()、call_special()、call_static()等用来调用不同类型的Java方法,不过这些函数最终都是调用call()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void JavaCalls::call(JavaValue* result, const methodHandle& method, JavaCallArguments* args, TRAPS) {
// Check if we need to wrap a potential OS exception handler around thread
// This is used for e.g. Win32 structured exception handlers
assert(THREAD->is_Java_thread(), "only JavaThreads can make JavaCalls");
// Need to wrap each and every time, since there might be native code down the
// stack that has installed its own exception handlers
os::os_exception_wrapper(call_helper, result, method, args, THREAD);
}
void
os::os_exception_wrapper(java_call_t f, JavaValue* value, const methodHandle& method,
JavaCallArguments* args, Thread* thread) {
f(value, method, args, thread);
}

f就是call()方法中传入的call_help()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void JavaCalls::call_helper(JavaValue* result, methodHandle* m, JavaCallArguments* args, TRAPS) {
......
StubRoutines::call_stub()(
(address)&link,
// (intptr_t*)&(result->_value), // see NOTE above (compiler problem)
result_val_address, // see NOTE above (compiler problem)
result_type,
method(),
entry_point,
args->parameters(),
args->size_of_parameters(),
CHECK
);
......
}

可见call_help()中最终是通过StubRoutines::call_stub()的返回值来调用Java方法的:

1
static CallStub call_stub() { return CAST_TO_FN_PTR(CallStub, _call_stub_entry); }

可见call_stub()返回了_call_stub_entry例程的地址,那么_call_stub_entry是何时生成的呢?其在generate_call_stub()方法中生成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
address generate_call_stub(address& return_address) {
StubCodeMark mark(this, "StubRoutines", "call_stub");
//汇编器会将生成的例程在内存中线性排列。所以取当前汇编器生成的上个例程最后一行汇编指令的地址,用来作为即将生成的新例程的首地址
address start = __ pc();
// stub code parameters / addresses
assert(frame::entry_frame_call_wrapper_offset == 2, "adjust this code");
bool sse_save = false;
const Address rsp_after_call(rbp, -4 * wordSize); // same as in generate_catch_exception()!
const int locals_count_in_bytes (4*wordSize);
//定义一些变量,用于保存一些调用方的信息,这四个参数放在被调用者堆栈中,即call_stub例程堆栈中,所以相对于call_stub例程的栈基址(rbp)为负数。(栈是向下增长),后面会用到这四个变量。
const Address mxcsr_save (rbp, -4 * wordSize);
const Address saved_rbx (rbp, -3 * wordSize);
const Address saved_rsi (rbp, -2 * wordSize);
const Address saved_rdi (rbp, -1 * wordSize);
//传参,放在调用方堆栈中,所以相对call_stub例程的栈基址为正数,可以理解为调用方在调用call_stub例程之前,会将传参都放在自己的堆栈中,这样call_stub例程中就可以直接基于栈基址进行偏移取用了。
const Address result (rbp, 3 * wordSize);
const Address result_type (rbp, 4 * wordSize);
const Address method (rbp, 5 * wordSize);
const Address entry_point (rbp, 6 * wordSize);
const Address parameters (rbp, 7 * wordSize);
const Address parameter_size(rbp, 8 * wordSize);
const Address thread (rbp, 9 * wordSize); // same as in generate_catch_exception()!
sse_save = UseSSE > 0;
//enter()对应的方法如下,用来保存调用方栈基址,并将call_stub栈基址更新为当前栈顶地址,c语言编译器其实在调用方法前都会插入这件事,这里JVM相对于借用了这种思想。
---------------------------------------------
| void MacroAssembler::enter() { |
| push(rbp); |
| mov(rbp, rsp); |
| } |
---------------------------------------------
__ enter();
//接下来计算并分配call_stub堆栈所需栈大小。
//先将参数数量放入rcx寄存器。
__ movptr(rcx, parameter_size); // parameter counter
//shl用于左移,这里将rcx中的值左移了Interpreter::logStackElementSize位,在64位平台,logStackElementSize=3;在32位平台,logStackElementSize=2;所以在64位平台上,rcx = rcx * 8, 即每个参数占用8字节;32位平台rcx = rcx *4 ,即每个参数占4个字节。
__ shlptr(rcx, Interpreter::logStackElementSize); // convert parameter count to bytes
// locals_count_in_bytes 在上面有定义:const int locals_count_in_bytes (4*wordSize);这四个字节其实就是上面用来保存调用方信息所占空间。
__ addptr(rcx, locals_count_in_bytes); // reserve space for register saves
//rcx现在保存了计算好的所需栈空间,将保存栈顶地址的寄存器rsp减去rcx,即向下扩展栈。
__ subptr(rsp, rcx);
//引用《揭秘Java虚拟机》:为了加速内存寻址和回收,物理机器在分配堆栈空间时都会进行内存对齐,JVM也借用了这个思想。JVM中是按照两个字节,即16位进行对齐的:const int StackAlignmentInBytes = (2*wordSize);
__ andptr(rsp, -(StackAlignmentInBytes)); // Align stack
//将调用方的一些信息,保存到栈中分配的地址处,最后会再次还原到寄存器中
__ movtr(saved_rdi, rdi);
__ movptr(saved_rsi, rsi);
__ movptr(saved_rbx, rbx);
......
......
//接下来就要进行参数压栈了;
Label parameters_done;
//检查参数数量是否为0,为0则直接跳到标号parameters_done处。
__ movl(rcx, parameter_size); // parameter counter
__ testl(rcx, rcx);
__ jcc(Assembler::zero, parameters_done);
Label loop
//将参数首地址放到寄存器rdx中,并将rbx置0;
__ movptr(rdx, parameters); // parameter pointer
__ xorptr(rbx, rbx);
//标号loop处
__ BIND(loop);
//此处开始循环;从最后一个参数倒序往前进行参数压栈,初始时,rcx = parameter_size;要注意,这里的参数是指java方法所需的参数,而不是call_stub例程所需参数!
//将(rdx + rcx * stackElementScale()- wordSize )移到 rax 中,(rdx + rcx * stackElementScale()- wordSize )指向了要压栈的参数。
__ movptr(rax, Address(rdx, rcx, Interpreter::stackElementScale(), -wordSize));
//再从rax中转移到(rsp + rbx * stackElementScale()) 处,expr_offset_in_bytes(0) = 0;这里是基于栈顶地址进行偏移寻址的,最后一个参数会被压到栈顶处。第一个参数会被压到rsp + (parameter_size-1)* stackElementScale()处。
__ movptr(Address(rsp, rbx, Interpreter::stackElementScale(),
Interpreter::expr_offset_in_bytes(0)), rax); // store parameter
//更新rbx
__ increment(rbx);
//自减rcx,当rcx不为0时,继续跳往loop处循环执行。
__ decrement(rcx);
__ jcc(Assembler::notZero, loop);
//标号parameters_done处
__ BIND(parameters_done);
//接下来要开始调用Java方法了。
//将调用java方法的entry_point例程所需的一些参数保存到寄存器中
__ movptr(rbx, method); // get Method*
__ movptr(rax, entry_point); // get entry_point
__ mov(rsi, rsp); // set sender sp
//跳往entry_point例程执行
__ call(rax);
......
}

上面最后会跳往entry_point例程执行,这个entry_point是在JavaCalls::call_helper()中:

1
address entry_point = method->from_interpreted_entry();

entry_point是从当前要执行的方法中获得的:

1
2
3
4
5
/openjdk/hotspot/src/share/vm/oops/method.hpp
volatile address from_interpreted_entry() const{
return (address)OrderAccess::load_ptr_acquire(&_from_interpreted_entry);
}

那么_from_interpreted_entry是何时赋值呢?method.hpp中有这样一个set方法:

1
2
3
4
void set_interpreter_entry(address entry) {
_i2i_entry = entry;
_from_interpreted_entry = entry;
}

我们来看看何时调用了这个set方法:

1
2
3
4
5
6
7
8
9
10
// Called when the method_holder is getting linked. Setup entrypoints so the method
// is ready to be called from interpreter, compiler, and vtables.
void Method::link_method(methodHandle h_method, TRAPS) {
......
address entry = Interpreter::entry_for_method(h_method);
assert(entry != NULL, "interpreter entry must be non-null");
// Sets both _i2i_entry and _from_interpreted_entry
set_interpreter_entry(entry);
......
}

在方法链接时,会去设置方法的entry_point,entry_point是由Interpreter::entry_for_method(h_method)得到的:

1
static address entry_for_method(methodHandle m) { return entry_for_kind(method_kind(m)); }

首先通过method_kind()拿到方法类型,接着调用entry_for_kind()

1
2
3
static address entry_for_kind(MethodKind k){
return _entry_table[k];
}

这里直接返回了_entry_table数组中对应方法类型索引的entry_point地址,给数组中元素赋值有专门的方法:

1
2
3
void AbstractInterpreter::set_entry_for_kind(AbstractInterpreter::MethodKind kind, address entry) {
_entry_table[kind] = entry;
}

那么何时调用set_entry_for_kind()呢?在TemplateInterpreterGenerator::generate_all()中,generate_all()会调用generate_method_entry()去生成每种方法的entry_point,所有Java方法的执行,都会通过对应类型的entry_point例程来辅助。

说明